/*
 * vendor/amlogic/media/common/codec_mm/secmem.c
 *
 * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/dma-buf.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/compat.h>

#include "secmem.h"

static int debug;
module_param(debug, int, 0644);

#define DEVICE_NAME "secmem"
#define CLASS_NAME "secmem"

#define dprintk(level, fmt, arg...)                                                                                    \
    do {                                                                                                               \
        if (debug >= level)                                                                                            \
            pr_info(DEVICE_NAME ": %s: " fmt, __func__, ##arg);                                                        \
    } while (0)

#define pr_dbg(fmt, args...) dprintk(6, fmt, ##args)
#define pr_error(fmt, args...) dprintk(1, fmt, ##args)
#define pr_inf(fmt, args...) dprintk(8, fmt, ##args)
#define pr_enter() pr_inf("enter")

struct ksecmem_attachment {
    struct sg_table sgt;
    enum dma_data_direction dma_dir;
};

static int dev_no;
static struct device *secmem_dev;

static int secmem_dma_attach(struct dma_buf *dbuf, struct dma_buf_attachment *attachment)
{
    struct ksecmem_attachment *attach;
    struct secmem_block *info = dbuf->priv;
    struct sg_table *sgt;
    struct page *page;
    phys_addr_t phys = info->paddr;
    int ret;

    pr_enter();
    attach = kzalloc(sizeof(*attach), GFP_KERNEL);
    if (!attach) {
        pr_error("kzalloc failed\n");
        return -ENOMEM;
    }

    sgt = &attach->sgt;
    ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
    if (ret) {
        kfree(attach);
        return -ENOMEM;
    }

    page = phys_to_page(phys);
    sg_set_page(sgt->sgl, page, PAGE_ALIGN(info->size), 0);

    attach->dma_dir = DMA_NONE;
    attachment->priv = attach;

    return 0;
}

static void secmem_dma_detach(struct dma_buf *dbuf, struct dma_buf_attachment *attachment)
{
    struct ksecmem_attachment *attach = attachment->priv;
    struct sg_table *sgt;

    pr_enter();
    if (!attach) {
        return;
    }

    sgt = &attach->sgt;

    sg_free_table(sgt);
    kfree(attach);
    attachment->priv = NULL;
}

static struct sg_table *secmem_dma_map_dma_buf(struct dma_buf_attachment *attachment, enum dma_data_direction dma_dir)
{
    struct ksecmem_attachment *attach = attachment->priv;
    struct secmem_block *info = attachment->dmabuf->priv;
    struct mutex *lock = &attachment->dmabuf->lock;
    struct sg_table *sgt;

    pr_enter();
    mutex_lock(lock);
    sgt = &attach->sgt;
    if (attach->dma_dir == dma_dir) {
        mutex_unlock(lock);
        return sgt;
    }

    sgt->sgl->dma_address = info->paddr;
#ifdef CONFIG_NEED_SG_DMA_LENGTH
    sgt->sgl->dma_length = PAGE_ALIGN(info->size);
#else
    sgt->sgl->length = PAGE_ALIGN(info->size);
#endif
    pr_dbg("nents %d, %x, %d, %d\n", sgt->nents, info->paddr, sg_dma_len(sgt->sgl), info->size);
    attach->dma_dir = dma_dir;

    mutex_unlock(lock);
    return sgt;
}

static void secmem_dma_unmap_dma_buf(struct dma_buf_attachment *attachment, struct sg_table *sgt,
                                     enum dma_data_direction dma_dir)
{
    pr_enter();
}

static void secmem_dma_release(struct dma_buf *dbuf)
{
    struct secmem_block *info;

    pr_enter();
    info = (struct secmem_block *)dbuf->priv;
    pr_dbg("handle:%x\n", info->handle);
    kfree(info);
}

static void *secmem_dma_kmap_atomic(struct dma_buf *dbuf, unsigned long n)
{
    pr_enter();
    return NULL;
}

static void *secmem_dma_kmap(struct dma_buf *dbuf, unsigned long n)
{
    pr_enter();
    return NULL;
}

static int secmem_dma_mmap(struct dma_buf *dbuf, struct vm_area_struct *vma)
{
    pr_enter();
    return -EINVAL;
}

static struct dma_buf_ops secmem_dmabuf_ops = {.attach = secmem_dma_attach,
                                               .detach = secmem_dma_detach,
                                               .map_dma_buf = secmem_dma_map_dma_buf,
                                               .unmap_dma_buf = secmem_dma_unmap_dma_buf,
                                               .release = secmem_dma_release,
                                               .mmap = secmem_dma_mmap};

static struct dma_buf *get_dmabuf(struct secmem_block *info, unsigned long flags)
{
    struct dma_buf *dbuf;
    DEFINE_DMA_BUF_EXPORT_INFO(exp_info);

    pr_dbg("export handle:%x, paddr:%x, size:%d\n", info->handle, info->paddr, info->size);
    exp_info.ops = &secmem_dmabuf_ops;
    exp_info.size = info->size;
    exp_info.flags = flags;
    exp_info.priv = (void *)info;
    dbuf = dma_buf_export(&exp_info);
    if (IS_ERR(dbuf)) {
        return NULL;
    }
    return dbuf;
}

static long secmem_export(unsigned long args)
{
    int ret;
    struct secmem_block *info;
    struct dma_buf *dbuf;
    int fd;

    pr_enter();
    info = kzalloc(sizeof(*info), GFP_KERNEL);
    if (!info) {
        pr_error("kmalloc failed\n");
        goto error_alloc_object;
    }
    ret = copy_from_user((void *)info, (void __user *)args, sizeof(struct secmem_block));
    if (ret) {
        pr_error("copy_from_user failed\n");
        goto error_copy;
    }

    dbuf = get_dmabuf(info, 0);
    if (!dbuf) {
        pr_error("get_dmabuf failed\n");
        goto error_export;
    }

    fd = dma_buf_fd(dbuf, O_CLOEXEC);
    if (fd < 0) {
        pr_error("dma_buf_fd failed\n");
        goto error_fd;
    }

    return fd;
error_fd:
    dma_buf_put(dbuf);
error_export:
error_copy:
    kfree(info);
error_alloc_object:
    return -EFAULT;
}

static long secmem_get_handle(unsigned long args)
{
    int ret;
    long res = -EFAULT;
    int fd;
    struct dma_buf *dbuf;
    struct secmem_block *info;

    pr_enter();
    ret = copy_from_user((void *)&fd, (void __user *)args, sizeof(fd));
    if (ret) {
        pr_error("copy_from_user failed\n");
        goto error_copy;
    }
    dbuf = dma_buf_get(fd);
    if (IS_ERR(dbuf)) {
        pr_error("dma_buf_get failed\n");
        goto error_get;
    }
    info = dbuf->priv;
    res = (long)(info->handle & (0xffffffff));
    dma_buf_put(dbuf);
error_get:
error_copy:
    return res;
}

static long secmem_get_phyaddr(unsigned long args)
{
    int ret;
    long res = -EFAULT;
    int fd;
    struct dma_buf *dbuf;
    struct secmem_block *info;

    pr_enter();
    ret = copy_from_user((void *)&fd, (void __user *)args, sizeof(fd));
    if (ret) {
        pr_error("copy_from_user failed\n");
        goto error_copy;
    }
    dbuf = dma_buf_get(fd);
    if (IS_ERR(dbuf)) {
        pr_error("dma_buf_get failed\n");
        goto error_get;
    }
    info = dbuf->priv;
    res = (long)(info->paddr & (0xffffffff));
    dma_buf_put(dbuf);
error_get:
error_copy:
    return res;
}

static long secmem_import(unsigned long args)
{
    int ret;
    long res = -EFAULT;
    int fd;
    struct dma_buf *dbuf;
    struct dma_buf_attachment *attach;
    struct sg_table *sgt;
    dma_addr_t paddr;

    pr_enter();
    ret = copy_from_user((void *)&fd, (void __user *)args, sizeof(fd));
    if (ret) {
        pr_error("copy_from_user failed\n");
        goto error_copy;
    }
    dbuf = dma_buf_get(fd);
    if (IS_ERR(dbuf)) {
        pr_error("dma_buf_get failed\n");
        goto error_get;
    }
    attach = dma_buf_attach(dbuf, secmem_dev);
    if (IS_ERR(attach)) {
        pr_error("dma_buf_attach failed\n");
        goto error_attach;
    }
    sgt = dma_buf_map_attachment(attach, DMA_FROM_DEVICE);
    if (IS_ERR(sgt)) {
        pr_error("dma_buf_map_attachment failed\n");
        goto error_map;
    }

    paddr = sg_dma_address(sgt->sgl);

    res = (long)(paddr & (0xffffffff));
    dma_buf_unmap_attachment(attach, sgt, DMA_FROM_DEVICE);
error_map:
    dma_buf_detach(dbuf, attach);
error_attach:
    dma_buf_put(dbuf);
error_get:
error_copy:
    return res;
}
static int secmem_open(struct inode *inodep, struct file *filep)
{
    pr_enter();
    return 0;
}

static ssize_t secmem_read(struct file *filep, char *buffer, size_t len, loff_t *offset)
{
    pr_enter();
    return 0;
}

static ssize_t secmem_write(struct file *filep, const char *buffer, size_t len, loff_t *offset)
{
    pr_enter();
    return 0;
}

static int secmem_release(struct inode *inodep, struct file *filep)
{
    pr_enter();
    return 0;
}

static long secmem_ioctl(struct file *filep, unsigned int cmd, unsigned long args)
{
    long ret = -EINVAL;

    pr_inf("cmd %x\n", cmd);
    switch (cmd) {
        case SECMEM_EXPORT_DMA:
            ret = secmem_export(args);
            break;
        case SECMEM_GET_HANDLE:
            ret = secmem_get_handle(args);
            break;
        case SECMEM_GET_PHYADDR:
            ret = secmem_get_phyaddr(args);
            break;
        case SECMEM_IMPORT_DMA:
            ret = secmem_import(args);
            break;
        default:
            break;
    }
    return ret;
}

#ifdef CONFIG_COMPAT
static long secmem_compat_ioctl(struct file *filep, unsigned int cmd, unsigned long args)
{
    unsigned long ret;

    args = (unsigned long)compat_ptr(args);
    ret = secmem_ioctl(filep, cmd, args);
    return ret;
}
#endif

const struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = secmem_open,
    .read = secmem_read,
    .write = secmem_write,
    .release = secmem_release,
    .unlocked_ioctl = secmem_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = secmem_compat_ioctl,
#endif
};

static struct class secmem_class = {
    .name = CLASS_NAME,
};

int __init secmem_init(void)
{
    int ret;

    ret = register_chrdev(0, DEVICE_NAME, &fops);
    if (ret < 0) {
        pr_error("register_chrdev failed\n");
        goto error_register;
    }
    dev_no = ret;
    ret = class_register(&secmem_class);
    if (ret < 0) {
        pr_error("class_register failed\n");
        goto error_class;
    }

    secmem_dev = device_create(&secmem_class, NULL, MKDEV(dev_no, 0), NULL, DEVICE_NAME);
    if (IS_ERR(secmem_dev)) {
        pr_error("device_create failed\n");
        ret = PTR_ERR(secmem_dev);
        goto error_create;
    }
    pr_dbg("init done\n");
    return 0;
error_create:
    class_unregister(&secmem_class);
error_class:
    unregister_chrdev(dev_no, DEVICE_NAME);
error_register:
    return ret;
}

void __exit secmem_exit(void)
{
    device_destroy(&secmem_class, MKDEV(dev_no, 0));
    class_unregister(&secmem_class);
    class_destroy(&secmem_class);
    unregister_chrdev(dev_no, DEVICE_NAME);
    pr_dbg("exit done\n");
}

module_init(secmem_init);
module_exit(secmem_exit);
MODULE_LICENSE("GPL");
